﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using CommonTypes;
using System.Runtime.Remoting;
using System.Diagnostics;
using System.Net.Sockets;
using LinFu.DynamicProxy;
using System.Reflection;


namespace ServersManager
{
    class ServersManagerProgram
    {
        private static int _currentUrl = 0;
        private static List<string> _urls = new List<string>();
        private static IServer server;

        static void Main(string[] args)
        {
            int port = 65535;
            TcpChannel channel = new TcpChannel(port);
            ChannelServices.RegisterChannel(channel, true);

            RemotingConfiguration.RegisterWellKnownServiceType(typeof(ServersManager), "ServersManager", WellKnownObjectMode.Singleton);

            ServersManager serversManager = (ServersManager)Activator.GetObject(typeof(ServersManager), "tcp://localhost:" + port + "/ServersManager");
            serversManager.connectReplica(args[0], args[1]);

            // set up the dynamic proxy and connect to the server
            ProxyFactory factory = new ProxyFactory();
            ExceptionWrapper wrapper = new ExceptionWrapper(Activator.GetObject(typeof(IServer),
                _urls[_currentUrl]));
            server = factory.CreateProxy<IServer>(wrapper);

            // register the exception handler
            DefaultHandler handler = new DefaultHandler();
            handler.Retries = _urls.Count - 1;
            handler.ExceptionTypes.Add(typeof(SocketException));
            //handler.ExceptionTypes.Add( typeof( TargetInvocationException ) );
            handler.OnHandledException += new OnHandledExceptionDelegate(handler_OnHandledException);
            wrapper.AddHandler(handler);

            Console.WriteLine("ServersManager initiated!");
            Console.ReadLine();
        }

        static void handler_OnHandledException(IExceptionHandler handler, Exception e,
            InvocationInfo info, ref object o)
        {
            string url = _urls[++_currentUrl % _urls.Count];
            Console.WriteLine("Handler [{0}]: Failing over to: {1}", e.GetType().Name, url);
            o = Activator.GetObject(typeof(IServer), url);
        }

        public static List<string> Urls {
            get { return _urls; }
        }

        public static int CurrentUrl {
            get { return _currentUrl; }
        }

        public static IServer Server {
            get { return server; }
        }
    }

    public class ServersManager : MarshalByRefObject, IServersManager {

        private static List<ServerInfo> servers = new List<ServerInfo>();

        public void connectReplica(String serverName, String ipPort) {
            IServer server;
            foreach (ServerInfo serverInfo in servers)
                if (serverInfo.ServerName.Equals(serverName))
                    return;

            Process serverProcess = new Process();
            serverProcess.StartInfo.FileName = "ServerApp.exe";
            serverProcess.StartInfo.Arguments = serverName + " " + ipPort;
            serverProcess.Start();


            server = (IServer)Activator.GetObject(typeof(IServer), "tcp://"+ipPort+"/Server");
            ServerInfo serverinfo = new ServerInfo(serverName,server, ipPort, serverProcess);
            servers.Add(serverinfo);
            ServersManagerProgram.Urls.Add(string.Format("tcp://"+ipPort+"/Server"));
            
            if (servers.Count == 2) {
                servers.ElementAt<ServerInfo>(0).Server.setBackup("tcp://" + ipPort + "/Server");
                servers.ElementAt<ServerInfo>(0).Server.sendUsersList();
                Console.WriteLine("Mybackup " + "tcp://" + ipPort + "/Server");
            }
            if (servers.Count == 3)
            {
                servers.ElementAt<ServerInfo>(1).Server.setBackup("tcp://" + ipPort + "/Server");
                servers.ElementAt<ServerInfo>(1).Server.sendUsersList();
                Console.WriteLine("Mybackup " + "tcp://" + ipPort + "/Server");
            }

        }

        public void disconnectReplica(String serverName) {
            int i=0;
            foreach (ServerInfo serverInfo in servers){
                if (serverInfo.ServerName.Equals(serverName)){
                    serverInfo.ServerProcess.Kill();
                    break;
                }
                i++;
            }
            servers.RemoveAt(i);
            if (servers.Count == 2)
            {
                ServerInfo backup = servers.Last();
                servers.ElementAt<ServerInfo>(0).Server.setBackup("tcp://" + backup.IpPort + "/Server");
                Console.WriteLine("Mybackup " + "tcp://" + backup.IpPort + "/Server");
            }
        }

        public String activeServer() {
            while (true)
            {
                try
                {
                    ServersManagerProgram.Server.echo();
                    return ServersManagerProgram.Urls[ServersManagerProgram.CurrentUrl];
                }
                catch (Exception e)
                {
                    Console.WriteLine("Got Exception: {0}", e.Message);
                }
            }
        }

        public class ServerInfo
        {
            private String serverName;
            private IServer server;
            private String ipPort;
            private Process serverProcess;


            public ServerInfo(String serverName, IServer server, String ipPort, Process process)
            {
                this.serverName = serverName;
                this.server = server;
                this.ipPort = ipPort;
                this.serverProcess = process;
            }

            public IServer Server
            {
                get { return server; }
            }

            public String ServerName {
                get { return serverName; }
            }

            public String IpPort
            {
                get { return ipPort; }
                set { ipPort = value; }
            }

            public Process ServerProcess
            {
                get { return serverProcess; }
            }
        }
    }

    public interface IExceptionHandler
    {
        IList<Type> ExceptionTypes { get; }
        void HandleException(Exception e, InvocationInfo info, ref object o);
        int Retries { get; }
        bool RethrowOnFail { get; }
    }

    public delegate void OnHandledExceptionDelegate(IExceptionHandler handler, Exception e,
        InvocationInfo info, ref object o);

    public class DefaultHandler : IExceptionHandler
    {
        public DefaultHandler()
        {
            Retries = 0;
            RethrowOnFail = true;
        }

        public event OnHandledExceptionDelegate OnHandledException;
        #region IExceptionHandler Members

        public virtual void HandleException(Exception e, InvocationInfo info, ref object o)
        {
            if (OnHandledException != null)
                OnHandledException(this, e, info, ref o);
            else
                Console.WriteLine("No handlers registered");
        }

        public virtual int Retries { get; set; }
        public virtual bool RethrowOnFail { get; set; }

        private readonly List<Type> _exceptionTypes = new List<Type>();
        public IList<Type> ExceptionTypes
        {
            get { return _exceptionTypes; }
        }

        #endregion
    }

    public class ExceptionWrapper : IInvokeWrapper
    {
        private object _object;
        public ExceptionWrapper(Object o)
        {
            _object = o;
        }

        public void AddHandler(IExceptionHandler handler)
        {
            _handlers.Add(handler);
            this.handler = handler;
        }

        public object Target { get { return _object; } set { _object = value; } }
        private IExceptionHandler handler;
        private readonly List<IExceptionHandler> _handlers = new List<IExceptionHandler>();

        #region IInvokeWrapper Members

        public void AfterInvoke(InvocationInfo info, object returnValue)
        {
           
        }
        public void BeforeInvoke(InvocationInfo info)
        {
            
        }

        public object DoInvoke(InvocationInfo info)
        {
            object result = null;
            bool retry = true;
            int retries = -1;

            while (retry)
            {
                try
                {
                    retry = false;
                    result = info.TargetMethod.Invoke(_object, info.Arguments);
                }
                catch (Exception e)
                {
                    Exception ex = e;
                    if (e is TargetInvocationException)
                        ex = e.InnerException;

                    Type type = ex.GetType();
                    foreach (IExceptionHandler handler in _handlers)
                    {
                        if (handler.ExceptionTypes.Contains(type))
                        {
                            handler.HandleException(ex, info, ref _object);
                            if (retries == -1)
                                retries = handler.Retries;

                            if (retries-- > 0)
                            {
                                retry = true;
                                break;
                            }
                            else
                            {
                                if (handler.RethrowOnFail)
                                    throw ex;
                            }
                        }
                    }
                    if (!retry)
                        throw ex;
                }
            }
            return result;
        }

        #endregion
    }
}
